import re
import sys
import asyncio
import asyncio.streams


class Listener:
    # (Tornado) listener
    
    def __init__(self, token=None):
        self.msg_queue = asyncio.JoinableQueue()
        self.token = token
    
    def get_msg(self, callback):
        def coro(future):
            result = (yield from self.msg_queue.get())
            future.set_result(result)
        
        def got_result(future):
            callback(future.result())
            self.msg_queue.task_done()
        
        future = asyncio.Future()
        asyncio.Task(coro(future))
        future.add_done_callback(got_result)


class MyServer:
    
    class Client:
        def __init__(self, reader, writer, name=None):
            self.reader = reader
            self.writer = writer
            self.name = name
            
    def __init__(self):
        self.server = None
        self.clients = {} # task -> (reader, writer)
        self.listeners = {} # token -> listener

    def _accept_client(self, client_reader, client_writer):
        client = self.Client(client_reader, client_writer)
        task = asyncio.Task(self._handle_client(client))
        self.clients[task] = client

        def client_done(task):
            del self.clients[task]
        task.add_done_callback(client_done)

    @asyncio.coroutine
    def _handle_client(self, client):
        
        def is_login(line):
            match = re.search(r'^Auth::\s(\S+)\r\n', line)
            return match and match.group(1)
        
        def is_logout(line):
            return re.search(r'^End\r\n', line)
        
        def parse(line):
            match = re.search(r'^(\S+)::\s(\S+)\r\n', line)
            return match and (match.group(1), match.group(2))
        
        while True:
            data = (yield from client.reader.readline()).decode("utf-8")
            if not data:
                print('client %s disconnected' % client.name or '')
                break
            if not client.name:
                client.name = is_login(data)
                continue
            elif is_logout(data):
                client.name = None
                continue
            elif not parse(data):
                continue
            key, value = parse(data)

            for listener in self.listeners.values():
                def coro(listener=listener, key=key, value=value, name=client.name):
                    yield from listener.msg_queue.join()
                    yield from listener.msg_queue.put(
                        (name, key, value))
                asyncio.Task(coro())

    def start(self, loop):
        self.server = loop.run_until_complete(
            asyncio.streams.start_server(self._accept_client,
                                         '127.0.0.1', 12345,
                                         loop=loop))

    def stop(self, loop):
        if self.server is not None:
            self.server.close()
            loop.run_until_complete(self.server.wait_closed())
            self.server = None
    
    def listen(self, token):
        return self.listeners.setdefault(
            token, Listener(token))
    
    def unsubscribe(self, listener):
        self.listeners.pop(listener.token)